Linux给应用程序提供了丰富的api,但是有时候我们需要跟硬件交互,访问一些特权级信息,所以可以使用编写内核模块这种方式。
另外Linux是宏内核结构,效率非常高,没有微内核那样各个模块之间的通讯损耗,但是又不能方便的对内核进行改动,可扩展性和可维护性比较差,内核模块提供了一种动态加载代码的方式,弥补了宏内核的不足。
步骤
- 首先需要xxx.c原文件存放代码,Makefile用来编译xxx.c文件。
编写内核模块源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// lkm_example.c
//__init会将lkm_example_init函数标记为初始化函数,模块被装载到内核时会调用该函数。
static int __init lkm_example_init(void) {
printk(KERN_INFO "Hello, World!\n"); //
return 0;
}
//模块被卸载时被调用
static void __exit lkm_example_exit(void) {
printk(KERN_INFO "Goodbye, World!\n");
}
module_init(lkm_example_init); //引导内核加载模块
module_exit(lkm_example_exit); //引导内核卸载模块
MODULE_LICENSE("GPL"); //必选项 模块许可证,如果没有添加模块许可证,会收到内核被污染的警告
MODULE_AUTHOR("YIFEI"); //可选 模块作者
MODULE_DESCRIPTION("linux module"); //可选 模块描述
MODULE_VERSION("0.01"); //可选项 模块版本编写Makefile文件
1
2
3
4
5
6
7
8
9
10obj-m += lkm_example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
-C选项:此选项指定内核源码的位置,make在编译时将会进入内核源码目录,执行编译,编译完成时返回。
这个build/目录是一个软连接,链接到源码头文件的安装位置。
M=$(PWD):需要编译的模块源文件地址
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean执行make编译模块
1
$ make
装载模块
1
$ sudo insmod lkm_example.ko
查看装载的模块
1
$ lsmod
卸载模块
1
$ sudo rmmod lkm_example.ko
查看打印的日志
1
2
3$ sudo dmesg
[75789.276382] Hello, World!
[75789.307013] Goodbye, World!可以在Makefile最后添加以下代码,将测试流程自动化,每次只需执行 make test.
1
2
3
4
5test:
sudo dmesg -C
sudo insmod lkm_example.ko
sudo rmmod lkm_example.ko
dmesg
其他知识点
- 往内核模块传参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26static int pid = -1;
module_param(pid,int,S_IRUGO);
/*
在内核模块中定义一个全局变量,然后用module_param声明一下
参数一:表示参数的名字;
参数二:表示参数的类型;
参数三:表示参数的访问权限,S_IRUGO表示参数可以被所有人读取, 但是不能改变。
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
当往模块传数组类型的参数时
module_param_array(name, type, num, perm);
name:表示数组的名字;
type:表示参数的类型;
num :表示数组中元素数量;
perm:表示参数的访问权限;
*/
模块间函数调用
ma.c
1
2
3
4
5
6
7
8
9
10
11
12
int b=0;
void fun1(){
int a=0;
a++;
b++;
printk("%d %d \n",a,b);
}
EXPORT_SYMBOL(fun1);ma.c的Makefile
1
2
3
4
5
6
7obj-m += ma.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) cleanmb.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extern void fun1();
static int __init mb_init(void){
printk("hello\n");
fun1();
}
static int __exit mb_exit(void){
printk("goodbye\n");
}
module_init(mb_init);
module_exit(mb_exit);
MODULE_LICENSE("GPL");mb.c的Makefile
1
2
3
4
5
6
7
8
9-m += mb.o
KBUILD_EXTRA_SYMBOLS=/home/yifei/src/module_test/ma/Module.symvers #去该目录查找ma.ko的符号表
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
执行过程:1
2
3
4
5
6
7
8
9
10
11
12#要先插入ma.ko模块,再插入mb.ko.删除模块时顺序相反。
cd ma
make
insmod ma.ko
cd ../mb
make
insmod mb.ko
dmesg
rmmod mb.ko
rmmod ma.ko
Q&A
printk()使用方法。
1
2
3
4
5
6
7
8
9
10
11printk相比printf来说还多了个:日志级别的设置,用来控制printk打印的这条信息是否在终端上显示的,当日志级别的数值小于控制台级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台!
在我们内核中一共有8种级别,他们分别为:执行make编译内核模块时遇到签名验证失败时,在Makefile开始添加:
1
CONFIG_MODULE_SIG=n #关闭签名验证
根据pid获取可执行文件的绝对路径
https://www.cnblogs.com/ddk3000/p/5051111.html
参考
欢迎与我分享你的看法。
转载请注明出处:http://taowusheng.cn/